#pragma once

#include <vector>
#include <Ogre.h>

#include "QuadTree.h"




class TerrainManager
{
public:

	template<typename T>
	struct AddNodeToRender
	{
		AddNodeToRender(const size_t iWorldWidth, const size_t iMinCellSize, const size_t iMaxNumLevels,
			Ogre::SceneManager *iSceneMgr, const size_t iWorldMaxHeight,
			const float iTerrainGridStep) :
			mTerrainManager(nullptr),
			mCamera(iSceneMgr->getCamera("Camera")),
			mWorldSemiWidth(iWorldWidth/2),
			mMinCellSize(iMinCellSize),
			mWorldMaxHeight(iWorldMaxHeight),
			mTerrainGridStep(iTerrainGridStep),
			mLastLevel(iMaxNumLevels-1)
		{
			assert(iMaxNumLevels > 1);
		}

		void SetTerrainManager(TerrainManager* const iTerrainManager)
		{
			mTerrainManager = iTerrainManager;
		}

		// Funtore applicato ai nodi dell'albero partendo dalla root
		// viene richiamato ad ogni frame per decidere quale patch disegnare e a che livello di dettaglio
		bool operator()(const QuadTree<T>& iTree, const size_t iNodeId, const size_t iTreeLevel)
		{
			const QuadTreeNode<T>& node = iTree.GetNode(iNodeId);
			const size_t currentSemiWidth = mWorldSemiWidth/(0x01U << iTreeLevel);
			const float currentSemiWidthWorldSpace = currentSemiWidth*mTerrainGridStep;
			const Ogre::Vector2 currentCenterWorldSpace(node.mCenter * mTerrainGridStep);

			const Ogre::Vector3 patchAABBpoint1(currentCenterWorldSpace.x - static_cast<float>(currentSemiWidthWorldSpace), 
				currentCenterWorldSpace.y - static_cast<float>(currentSemiWidthWorldSpace), 0.0f);
			const Ogre::Vector3 patchAABBpoint2(currentCenterWorldSpace.x + static_cast<float>(currentSemiWidthWorldSpace),
				currentCenterWorldSpace.y + static_cast<float>(currentSemiWidthWorldSpace), static_cast<float>(mWorldMaxHeight));
			Ogre::AxisAlignedBox patchAABB(patchAABBpoint1, patchAABBpoint2);

			if(mCamera->isVisible(patchAABB))
			{
				const Ogre::Vector3 cameraPos = mCamera->getPosition();
				const float viewToPatchSqrDistance = Ogre::Vector3(currentCenterWorldSpace.x, currentCenterWorldSpace.y,
					 mWorldMaxHeight*0.5f).squaredDistance(cameraPos);
				if(viewToPatchSqrDistance < node.mData.minSqrDistance)
				{
					return true; // Continue traversing
				}

				mTerrainManager->AddPatch(node.mCenter.x, node.mCenter.y, mLastLevel-iTreeLevel);
				return false; // Stop traversing, patch will be rendered
				
			}

			return false; // Stop traversing, patch won't be rendered
		}


		TerrainManager* mTerrainManager;
		const Ogre::Camera* mCamera;
		const size_t mWorldMaxHeight;
		const float mTerrainGridStep;
		const size_t mWorldSemiWidth;
		const size_t mMinCellSize;
		const size_t mLastLevel;
	};


	static const size_t PATCH_NUM_VERTICES_PER_SIDE = 33U;
	static const size_t PATCH_INSTANCES_DEFAULT_NUM = 256U;

public:
	TerrainManager(Ogre::SceneManager *iSceneMgr, QuadTree<QTreeNodeData>* iQuadtree);
	~TerrainManager(void);

	void Update(const Ogre::FrameEvent& iEvent);

private:

	void AddPatch(const float iPosX, const float iPosY, const size_t iLOD);

	void BuildPatchGeometry(size_t iNumVerticesPerSide, std::vector<float>& oVertexData,
		std::vector<Ogre::int16>& oIndexData, size_t& oVerticesCount, size_t& oIndicesCount) const;
	void BuildPatchMesh(void);

	void ClearUnusedPatches(void);


	AddNodeToRender<QTreeNodeData> mAddNodeToRenderFunctor;
	std::vector<Ogre::InstancedEntity *> mInstances;

	Ogre::String mMaterialName;
	Ogre::MeshPtr mTerrainPatchMesh;
	Ogre::SceneManager* mSceneManager;
	Ogre::InstanceManager *mInstanceManager;

	QuadTree<QTreeNodeData>* mQuadtree;


	size_t mInstanceCursor;
};

